import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import service.FieldMappingService;
import service.FieldMappingServiceDefaultImpl;
import util.CommonUtil;
import util.FileUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 导出数据相关 导成文件形式
 */
@Slf4j(topic = "m.DataExport")
public class DataExport {

    /**
     * 导出数据文件中最大行数
     */
    public static Integer DATA_MAX_SIZE;
    /**
     * 导出文件路径
     */
    public static final String PATH_DATA = "data";

    private static Connection connection;
    /** 中间文件存放路径*/
    private static AtomicReference<String> dataDic = new AtomicReference<>(File.separator+"temp");


    /**
     * 主方法
     */
    public static void main(String[] args) throws IOException {

        String pass = null;
        String destStr = null;
        String needSaveYWJ = null;
        String needZip = null;
        final long start = System.currentTimeMillis();
        try {
            final Scanner scanner = new Scanner(System.in);
            log.info("***********数据库导出start*************");
            final AtomicInteger fetchSize = new AtomicInteger(1000);
            connection = CommonUtil.initDataBase(log, scanner, fetchSize);
            log.info("*************初始化database成功*************");

            //创建statement
            Statement statement = connection.createStatement();
            //设置fetch_size大小
            statement.setFetchSize(fetchSize.get());
            //查询数据库
            log.info("请输入要查询的表,多表以英文逗号分隔");
            String tmp = CommonUtil.receiveLine(log, scanner);
            assertNull(tmp);
            final String[] tablesArr = tmp.split(",");
            Set<String> tables = getSetTables(tablesArr);
            final int length = tables.size();
            log.info("导出格式：1（json格式） 2(insert格式)");
            tmp = CommonUtil.receiveLine(log, scanner);
            assertNull(tmp);
            final String exportType = tmp;
            log.info("请输入自定义处理表字段映射关系的业务类（没有可以不填,必须实现接口FieldMappingService）");
            tmp = CommonUtil.receiveLine(log, scanner);
            FieldMappingService fieldMappingService = new FieldMappingServiceDefaultImpl();
            if (tmp != null && !tmp.equals("")) {
                final Class<?> aClass = Class.forName(tmp);
                fieldMappingService = (FieldMappingService) aClass.newInstance();
            }
            log.info("生成中间文件的位置（默认:." + File.separator + dataDic.get() + File.separator + "；注意路径分隔符使用要正确&有后缀路径！！）");
            tmp = CommonUtil.receiveLine(log, scanner);
            if (StringUtils.isNotBlank(tmp)){
                dataDic.set(tmp.trim());
                final File tmpFileDir = new File(dataDic.get());
                if (!tmpFileDir.exists()){
                    boolean mkdirs = tmpFileDir.mkdirs();
                    log.info("创建中间文件文件夹：{}",mkdirs);
                }
            }
            log.info("导出的单个文件是否上限（1：是  0：否）");
            tmp = CommonUtil.receiveLine(log, scanner);
            assertNull(tmp);
            if (tmp.equals("1")) {
                log.info("每个数据文件最大值（数字）");
                tmp = CommonUtil.receiveLine(log, scanner);
                assertNull(tmp);
                DATA_MAX_SIZE = Integer.parseInt(tmp);
            }
            log.info("是否需要压缩（1：是  0：否）");
            tmp = CommonUtil.receiveLine(log, scanner);
            assertNull(tmp);
            needZip = tmp;
            if (needZip.equals("1")) {
                log.info("压缩密码（无密码直接回车）");
                tmp = CommonUtil.receiveLine(log, scanner);
                if (tmp != null) {
                    pass = tmp.trim();
                }
                log.info("压缩文件存放路径（默认:." + File.separator + PATH_DATA + File.separator + "；注意路径分隔符使用要正确&有后缀路径！！）");
                tmp = CommonUtil.receiveLine(log, scanner);
                if (tmp != null) {
                    destStr = tmp.trim();
                    final File tmpFileDir = new File(destStr);
                    if (!tmpFileDir.exists()){
                        boolean mkdirs = tmpFileDir.mkdirs();
                        log.info("创建压缩文件存放文件夹：{}",mkdirs);
                    }
                }
                log.info("是否需要保留源文件（1：是   0：否）");
                tmp = CommonUtil.receiveLine(log, scanner);
                assertNull(tmp);
                needSaveYWJ = tmp.trim();
            }
            int tableInt = 1;
            for (String table : tables) {
                final String countSql = "select count(1) from " + table;
                log.info("正在执行的SQL：" + countSql);
                ResultSet resultSet = statement.executeQuery(countSql);
                final int count = resultSet.next() ? resultSet.getInt(1) : 0;
                log.info(table + "表大小:" + count);
                final String listSql = "select * from " + table;
                log.info("正在执行的SQL：" + listSql);
                resultSet = statement.executeQuery(listSql);
                //获取返回结果集相关信息
                final ResultSetMetaData metaData = resultSet.getMetaData();
                //获取表一共几列？
                final int columnCount = metaData.getColumnCount();

                //设置导出文件逻辑
                final String fileBase = dataDic.get() + File.separator + table;
                String filePath = fileBase;
                if (DATA_MAX_SIZE != null) {
                    filePath += "_1";
                }
                String suffix = ".txt";
                filePath += suffix;
                log.info("文件路径：{}",filePath);
                //创建数据输出流
                final File file = new File(filePath);
                if (!file.exists()){
                    boolean newFile = file.createNewFile();
                    log.info("创建新文件：{}",newFile);
                }
                BufferedWriter dataWriter = new BufferedWriter(new FileWriter(file));
                try {
                    //导出具体数据逻辑
                    int dataInt = 1;
                    while (resultSet.next()) {
                        final Map<String, Object> row = new HashMap<>();
                        for (int i = 1; i < columnCount; i++) {
                            final Object object = resultSet.getObject(i);
                            final String columnName = metaData.getColumnName(i);
                            row.put(columnName, fieldMappingService.getValueByField(columnName, object));
                        }
                        log.info("共" + length + "张表需要执行，正在执行第" + tableInt + "张表" + table + "，当前表总数为" + count + "条，现在正在处理第" + dataInt + "条");
                        String line = "";
                        switch (exportType) {
                            case "1":
                                line = JSONObject.toJSONString(row, SerializerFeature.WriteMapNullValue);
                                break;
                            case "2":
                                line = "insert into " + table + "(";
                                final Set<String> keySet = row.keySet();
                                String values = "(";
                                for (String key : keySet) {
                                    line += key + ",";
                                    values += "'" + row.get(key) + "'" + ",";
                                }
                                line = line.substring(0, line.length() - 1) + ") values ";
                                line = line + values.substring(0, values.length() - 1) + ");";
                                break;
                            default:
                                throw new Exception("导出格式类型输入有误！");
                        }
                        dataWriter.write(line);
                        dataWriter.newLine();
                        dataWriter.flush();
                        if (DATA_MAX_SIZE != null && dataInt % DATA_MAX_SIZE == 0) {
                            int i = dataInt / DATA_MAX_SIZE;
                            filePath = fileBase + "_" + (i + 1) + suffix;
                            dataWriter.close();
                            dataWriter = new BufferedWriter(new FileWriter(new File(filePath)));
                        }
                        dataInt++;
                    }
                    tableInt++;
                } finally {
                    dataWriter.close();
                }
            }
            if (needZip.equals("1")) {
                log.info("正在执行压缩文件");
                FileUtils.zip(dataDic.get(), destStr, pass);
                log.info("压缩成功");
                if (needSaveYWJ.equals("0")) {
                    log.info("正在执行删除源文件");
                    FileUtils.deleteDirectory(new File(dataDic.get()));
                    log.info("删除成功");
                }
            }
            final long end = System.currentTimeMillis();
            log.info("***********数据库导出end,使用时长为：" + TimeUnit.MILLISECONDS.toSeconds(end - start) + "秒*************");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("1---{}", ExceptionUtils.getMessage(e));
        }

    }

    /**
     * 获取表，防止重名表
     */
    private static Set<String> getSetTables(String[] tablesArr) throws Exception {
        final Set<String> result = new HashSet<>();
        for (String str : tablesArr) {
            result.add(str);
        }
        return result;
    }

    /**
     * 校验输入空值
     */
    public static void assertNull(String tmp) throws Exception {
        CommonUtil.assertNull(tmp);
    }

}
